package hu.uszeged.inf.wlab.stunner.sync;

import hu.uszeged.inf.wlab.stunner.database.STUNnerContract;
import hu.uszeged.inf.wlab.stunner.database.dtos.DiscoveryDTO;
import hu.uszeged.inf.wlab.stunner.database.dtos.WifiInfoDTO;
import hu.uszeged.inf.wlab.stunner.sync.communication.Connector;
import hu.uszeged.inf.wlab.stunner.sync.communication.request.Record;
import hu.uszeged.inf.wlab.stunner.sync.communication.request.UploadRequest;
import hu.uszeged.inf.wlab.stunner.sync.communication.response.UploadResponse;
import hu.uszeged.inf.wlab.stunner.utils.GeneralResource;

import java.util.ArrayList;
import java.util.List;

import android.accounts.Account;
import android.annotation.SuppressLint;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SyncResult;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;

/**
 * Sync adapter responsible to upload the collected data.
 * 
 * @author szelezsant
 */
public class SyncAdapter extends AbstractThreadedSyncAdapter {

	// [szelezsant] sync state constants - currently not used
	// /** Constant for the sync started action. */
	// public static final String ACTION_SYNC_STARTED =
	// "hu.uszeged.inf.wlab.stunner.syncStarted";
	// /** Constant for the sync in progress action. */
	// public static final String ACTION_SYNC_STATUS =
	// "hu.uszeged.inf.wlab.stunner.syncStatus";
	// /** Constant for the sync finished action. */
	// public static final String ACTION_SYNC_FINISHED =
	// "hu.uszeged.inf.wlab.stunner.syncFinished";
	// /** Constant for the sync error action . */
	// public static final String ACTION_SYNC_ERROR =
	// "hu.uszeged.inf.wlab.stunner.syncError";
	// /** Constant for the intent extra sync error. */
	// public static final String EXTRA_SYNC_ERROR = "syncError";
	// /** Constant for the intent extra sync result. */
	// public static final String EXTRA_SYNC_RESULT = "syncResult";

	/** The {@link ContentResolver} instance. */
	private final ContentResolver contentResolver;

	/**
	 * Constructor meant to be used on systems older than version 3.0.
	 * 
	 * @param context
	 *            - the incoming context where the constructor was invoked.
	 * @param autoInitialize
	 *            - the flag to indicate if the adapter should be automatically
	 *            initialized.
	 */
	@SuppressLint("NewApi")
	public SyncAdapter(final Context context, final boolean autoInitialize) {
		super(context, autoInitialize);
		contentResolver = context.getContentResolver();
	}

	/**
	 * Constructor meant to be used on systems newer than version 3.0.
	 * 
	 * @param context
	 *            - the incoming context where the constructor was invoked.
	 * @param autoInitialize
	 *            - the flag to indicate if the adapter should be automatically
	 *            initialized.
	 * @param allowParallelSyncs
	 *            - flag to indicate if the adapter allows parallel
	 *            synchronization
	 */
	@SuppressLint("NewApi")
	public SyncAdapter(final Context context, final boolean autoInitialize, final boolean allowParallelSyncs) {
		super(context, autoInitialize, allowParallelSyncs);
		contentResolver = context.getContentResolver();
	}

	@Override
	public void onPerformSync(final Account account, final Bundle extras, final String authority, final ContentProviderClient provider,
			final SyncResult syncResult) {

		Log.i(getClass().getSimpleName(), "onPerformSync invoked - starting sync process...");
		// [szelezsant] currently not used
		// getContext().sendBroadcast(new Intent(ACTION_SYNC_STARTED));

		final Cursor pendingDiscoveries = contentResolver.query(STUNnerContract.Discovery.CONTENT_URI, null, STUNnerContract.Discovery.COLUMN_PENDING + "=?",
				new String[] { "1" }, null);

		if (null != pendingDiscoveries && pendingDiscoveries.getCount() > 0) {
			final List<Record> records = new ArrayList<Record>();
			while (pendingDiscoveries.moveToNext()) {
				final long batteryId = pendingDiscoveries.getLong(pendingDiscoveries.getColumnIndexOrThrow(STUNnerContract.Discovery.COLUMN_BATTERY_INFO_ID));
				final long wifiId = pendingDiscoveries.getLong(pendingDiscoveries.getColumnIndexOrThrow(STUNnerContract.Discovery.COLUMN_WIFI_INFO_ID));
				final long mobileId = pendingDiscoveries.getLong(pendingDiscoveries.getColumnIndexOrThrow(STUNnerContract.Discovery.COLUMN_MOBILE_INFO_ID));

				final Cursor batteryCursor = contentResolver.query(STUNnerContract.BatteryInfo.CONTENT_URI, null, STUNnerContract.BatteryInfo._ID + "=?",
						createWhereArgs(batteryId), null);
				final Cursor wifiCursor = contentResolver.query(STUNnerContract.WifiInfo.CONTENT_URI, null, STUNnerContract.WifiInfo._ID + "=?",
						createWhereArgs(wifiId), null);
				final Cursor mobileCursor = contentResolver.query(STUNnerContract.MobileNetInfo.CONTENT_URI, null, STUNnerContract.MobileNetInfo._ID + "=?",
						createWhereArgs(mobileId), null);

				final DiscoveryDTO discovery = STUNnerContract.Discovery.cursorToDTO(pendingDiscoveries);
				if (batteryCursor.moveToFirst()) {
					discovery.setBatteryDTO(STUNnerContract.BatteryInfo.cursorToDTO(batteryCursor));
				}
				if (wifiCursor.moveToFirst()) {
					discovery.setWifiDTO(STUNnerContract.WifiInfo.cursorToDTO(wifiCursor));
					// also need to hash the ssid and mac address.
					final WifiInfoDTO wifi = discovery.getWifiDTO();
					if (null != wifi) {
						wifi.setMacAddress(GeneralResource.createHashedId(wifi.getMacAddress()));
						wifi.setSsid(GeneralResource.createHashedId(wifi.getSsid()));
					}
				}
				if (mobileCursor.moveToFirst()) {
					discovery.setMobileDTO(STUNnerContract.MobileNetInfo.cursorToDTO(mobileCursor));
				}

				records.add(createRecord(discovery));

				batteryCursor.close();
				wifiCursor.close();
				mobileCursor.close();
			}

			uploadRecords(records);
		}

		// [szelezsant] currently not used
		// getContext().sendBroadcast(new Intent(ACTION_SYNC_FINISHED));

		contentResolver.notifyChange(STUNnerContract.Discovery.CONTENT_URI, null);
	}

	/**
	 * Uploads the given records.
	 * 
	 * @param records
	 *            - the records to upload.
	 */
	private void uploadRecords(final List<Record> records) {
		final UploadResponse response = new Connector(getContext()).post(new UploadRequest(GeneralResource.createHashedId(String
				.valueOf(Settings.Secure.ANDROID_ID)), records));
		if (response != null && response.getResult().isSuccess()) {
			Log.i(getClass().getSimpleName(), "uploading records was successful.");

			/* delete all associated records, plus the unmarked discoveries. */
			contentResolver.delete(STUNnerContract.WifiInfo.CONTENT_URI, null, null);
			contentResolver.delete(STUNnerContract.MobileNetInfo.CONTENT_URI, null, null);
			contentResolver.delete(STUNnerContract.BatteryInfo.CONTENT_URI, null, null);
			contentResolver.delete(STUNnerContract.Discovery.CONTENT_URI, STUNnerContract.Discovery.COLUMN_BOOKMARKED + "=?", new String[] { "0" });

			/* also update the bookmarked disoveries to uploaded state. */
			final ContentValues contentVals = new ContentValues();
			contentVals.put(STUNnerContract.Discovery.COLUMN_PENDING, false);
			contentResolver
					.update(STUNnerContract.Discovery.CONTENT_URI, contentVals, STUNnerContract.Discovery.COLUMN_BOOKMARKED + "=?", new String[] { "1" });

		} else if (null == response) {
			Log.e(getClass().getSimpleName(), "Response is null, probably connector failed with exception!");
		} else {
			Log.e(getClass().getSimpleName(), "Response contains error: " + response.getResult().getErrorCode().name());
			// TODO [szelezsant] error handling
		}
	}

	/**
	 * Produces a new string array to from the supplied identifier to be used as
	 * a where args clause in queries.
	 * 
	 * @param identifier
	 *            - the identifier which will be stringified.
	 * @return the new array
	 */
	private String[] createWhereArgs(final long identifier) {
		return new String[] { Long.toString(identifier) };
	}

	/**
	 * Creates a new {@link Record} object.
	 * 
	 * @param discovery
	 *            - the discovery object to be used as the record.
	 * @return new record.
	 */
	private Record createRecord(final DiscoveryDTO discovery) {
		return new Record(discovery);
	}

	/**
	 * Sends a broadcast message with the error caught.
	 * 
	 * @param exception
	 *            - the exception which was caught while trying to download the
	 *            server data.
	 */
	// [szelezsant] currently unused.
	// @SuppressWarnings("unused")
	// private void sendErrorBroadcast(final Exception exception) {
	// final Intent intent = new Intent(ACTION_SYNC_ERROR);
	// intent.putExtra(EXTRA_SYNC_ERROR, exception);
	// getContext().sendBroadcast(intent);
	// }

}
